import {
    ForwardRefRenderFunction,
    forwardRef,
    useCallback,
    useEffect,
    useImperativeHandle,
    useRef,
    useState
} from 'react';
import KChatView from '../chat-view';
import KChatInput from '../chat-input';
import { View } from '@tarojs/components';
import { throttle, getStore, isFunction, removeStore, isString, isArray } from '@material/helpers';
import { TComponent } from '@material/typeing';
import { getSafeSize } from '../../utils';
import { Message } from './message';
import { getHistoryList } from './message/history';
import $style from './index.scss';
import {
    ChatConfig,
    MessageErrorType,
    MessageInfo,
    MessageOptions,
    OutputRenderStatus,
    PluginType,
    ReconnectingInfo,
    SendBeforeItemType,
    SendParams
} from './message/type';
import { addRenderState, transformMessage } from './message/dispose';
import { ChatDataListComponent } from './list';
import { clsx } from 'react-runtime-clsx';
import Toast from '../toast';

import Taro, {
    useDidShow,
    getNetworkType,
    onNetworkStatusChange,
    offNetworkStatusChange
} from '@tarojs/taro';
import { PullLoadStatus } from '../chat-view/pull-load';
export interface ChatProps extends TComponent {
    onSendBefore?: SendBeforeItemType;
    config: ChatConfig;
    onInitBefore?: () => void;
    onInitAfter?: (params: MessageOptions & { isReset: boolean }) => void;
    onMessage?: (message: MessageInfo) => PluginType[];
    onError?: (info: MessageErrorType) => void;
    onOvertime?: (info: Record<string, any>) => void;
    onReconnecting?: (info: ReconnectingInfo) => void;
    onDestroy?: () => void;
    onHistory?: (data: MessageInfo[]) => MessageInfo[];
}
export type ChatRef = {
    scrollToBottom: () => void;
    push: (info: MessageInfo) => void;
    send: (value: string | SendParams) => void;
    resetChat: (val?: boolean) => void;
    newTopic?: (options?: Record<string, any>) => void;
    getLastMessage?: () => MessageInfo | void;
    updateLoadStatus?: (status: PullLoadStatus) => void;
};

const Chat: ForwardRefRenderFunction<ChatRef, ChatProps> = function (props, ref) {
    const {
        onMessage,
        onSendBefore,
        config,
        onInitBefore,
        onInitAfter,
        onHistory,
        onError,
        onOvertime,
        onReconnecting,
        onDestroy,
        className,
        style,
        children
    } = props;

    const [dataList, setDataList] = useState<MessageInfo[]>([]);
    const [outputStatus, setOutputStatus] = useState(() => '');
    const [size] = useState(() => getSafeSize());
    const [keybordHeight, setKeybordHeight] = useState(0);
    const [showPullHistory, setShowPullHistory] = useState(false);
    const virtuaRef = useRef<any>(null);
    const [showScrollBootom, setShowScrollBootom] = useState(false);
    const messgeRef = useRef<Message | null>(null);
    const useInputStatus = useRef('');
    const keyboardChangeHandle = (value: number) => {
        setKeybordHeight(value);
    };

    const filterSend = (params: SendParams) => {
        const { chatStatus, transformEvent = true } = { ...params };
        if (useInputStatus.current === 'start' && transformEvent && chatStatus === 'ai') {
            Toast({
                content: '智能助手回复中，请稍候',
                duration: 3
            });
            return true;
        }
        if (useInputStatus.current !== 'start' && transformEvent && chatStatus === 'ai') {
            updateUserInputStatus('start');
            return false;
        }
        return false;
    };
    const send = (value: string | SendParams) => {
        if (!messgeRef.current) {
            return;
        }
        const isConnected = messgeRef.current.getConnectStatus();
        if (!isConnected) {
            Toast({
                content: '网络繁忙，请稍后重试！',
                duration: 3
            });
            return;
        }

        if (isString(value)) {
            messgeRef.current.send({
                Content: value,
                filterSend
            });
            return;
        }
        messgeRef.current.send({
            ...value,
            filterSend
        });
    };
    const updatePullLoadStatus = function (status: PullLoadStatus) {
        if (!virtuaRef.current) {
            return;
        }
        virtuaRef.current.setExternalStatus(status);
    };
    const updateUserInputStatus = (val: any) => {
        useInputStatus.current = val;
        setOutputStatus(val);
    };
    const statusChangeHandle = useCallback((val: OutputRenderStatus) => {
        if (!messgeRef.current) {
            updateUserInputStatus(val);
            return;
        }
        const list = messgeRef.current.renderQueue;
        if (Array.isArray(list) && list.length) {
            while (list.length > 0) {
                const info = list.shift() as MessageInfo;
                pushToChat(info);
            }
        }
        if (list.length === 0 || !list) {
            updateUserInputStatus(val);
        }
    }, []);
    const getLastMessage = () => {
        if (isArray(dataList) && dataList.length) {
            return dataList[0];
        }
        return;
    };
    const scrollToBottom = () => {
        if (!virtuaRef.current) {
            return;
        }
        virtuaRef.current.scrollToBottom();
    };
    const resetPullLoadStatus = () => {
        if (!virtuaRef.current) {
            return;
        }
        virtuaRef.current.reset();
    };

    const historyApi = useCallback(async () => {
        const id = getStore('AI_Message_Last_id');
        if (!messgeRef.current) {
            return [1, []];
        }
        const [err, data] = await getHistoryList(messgeRef.current.serviceApi.historyApi, {
            count: 30,
            userid: messgeRef.current.chatParams.userId,
            token: messgeRef.current.chatParams.token,
            seq: id
        });
        const newData = isFunction(onHistory)
            ? onHistory(data as unknown as MessageInfo[])
            : (data as MessageInfo[]);

        return [err, newData.filter(Boolean), (data as unknown as MessageInfo[]).length];
    }, [onHistory]);

    const updateHistoryDataList = (list: MessageInfo[]) => {
        const newData = isFunction(onHistory) ? onHistory(list as unknown as MessageInfo[]) : list;
        const isHasData = isArray(newData) && newData.length;
        const newList = newData.filter(Boolean);
        if (isHasData && newList.length) {
            setDataList((pre) => {
                return [...pre, ...newList];
            });
        }
    };
    const refreshHandle = async () => {
        if (!showPullHistory) {
            return;
        }
        if (!messgeRef.current) {
            return;
        }
        const [err, data] = await historyApi();
        if (!err && isArray(data)) {
            const newList = updateHistoryDataList(data);
            setShowPullHistory(false);
            return !!(isArray(newList) && newList.length);
        }
        Toast({
            content: '历史消息加载失败请重试！',
            duration: 3
        });
    };

    const resetChat = (isCollaspe?: boolean) => {
        setDataList([]);
        if (isCollaspe) {
            removeStore('AI_Message_Last_id');
            setShowPullHistory(true);
        }
    };

    const updateHistoryHandle = (obj: any) => {
        const { data } = obj;
        updateHistoryDataList(data);
    };

    const pushToChat = (msg: MessageInfo) => {
        setDataList((pre: MessageInfo[]) => {
            const list = [...addRenderState(msg, pre)];
            return [...list];
        });
    };
    const push = (msg: Record<string, any> | Record<string, any>[]) => {
        const msgList = isArray(msg) ? msg : [msg];
        setDataList((pre: MessageInfo[]) => {
            return [...pre, ...msgList];
        });
    };
    useEffect(() => {
        isFunction(onInitBefore) && onInitBefore();
        messgeRef.current = new Message(config);
        messgeRef.current.init((params, initHistoryStatus, data) => {
            setShowPullHistory(true);
            resetChat();
            if (['error', 'reset'].includes(initHistoryStatus)) {
                setShowPullHistory(true);
                resetChat();
            }
            if (['init'].includes(initHistoryStatus)) {
                setShowPullHistory(false);
                updatePullLoadStatus('noMore');
                resetChat();
            }
            if (['session'].includes(initHistoryStatus)) {
                setShowPullHistory(false);
                const newData = isFunction(onHistory)
                    ? onHistory(data as unknown as MessageInfo[])
                    : data;
                setDataList([...newData.filter(Boolean)]);
            }

            isFunction(onInitAfter) &&
                onInitAfter({
                    ...params,
                    isReset: initHistoryStatus !== 'session'
                });
        });
        messgeRef.current.sendBefore(async (config: SendParams) => {
            const newConfig = isFunction(onSendBefore) ? onSendBefore(config) : config;
            return newConfig;
        });
        messgeRef.current.on('message', (info: MessageInfo) => {
            const plugins = isFunction(onMessage) ? onMessage(info) : [];
            const infoData = transformMessage(info, plugins);
            if (!messgeRef.current) {
                return;
            }
            if (!infoData && useInputStatus.current === 'start') {
                updateUserInputStatus('');
                return;
            }
            if (!infoData) {
                return;
            }
            const infoList =
                isArray(infoData) && infoData.length ? infoData : [infoData as MessageInfo];
            for (const item of infoList) {
                const itemInfo = messgeRef.current.filterRenderQuequ(item);
                if (itemInfo) {
                    pushToChat(item);
                }
            }
        });
        // 监听是否超时
        messgeRef.current.on('overtime', (errInfo: any) => {
            if (useInputStatus.current === 'start') {
                updateUserInputStatus('');
            }
            isFunction(onOvertime) && onOvertime(errInfo);
        });
        messgeRef.current.on('reconnecting', (info: ReconnectingInfo) => {
            isFunction(onReconnecting) && onReconnecting(info);
        });
        // 抛出所有错误
        messgeRef.current.on('error', (errInfo: any) => {
            if (useInputStatus.current === 'start') {
                updateUserInputStatus('');
            }
            isFunction(onError) && onError(errInfo);
        });
        return () => {
            if (messgeRef.current) {
                isFunction(messgeRef.current.destroyWebsocket) &&
                    messgeRef.current.destroyWebsocket();
                isFunction(messgeRef.current.destroy) && messgeRef.current.destroy();
                messgeRef.current = null;
            }
            removeStore('AI_Message_Last_id');
            isFunction(onDestroy) && onDestroy();
        };
    }, []);
    const newTopic = function (options?: Record<string, any>) {
        if (!messgeRef.current) {
            return;
        }
        if (isFunction(messgeRef.current?.newTopic)) {
            resetChat(true);
            messgeRef.current.newTopic(options);
            Taro.nextTick(() => {
                resetPullLoadStatus();
            });
        }
    };

    useImperativeHandle(ref, () => ({
        scrollToBottom,
        push,
        send,
        resetChat,
        newTopic,
        updatePullLoadStatus,
        getLastMessage,
        resetPullLoadStatus
    }));
    const scrollHandle = throttle((e: any) => {
        const { scrollTop } = e.detail;
        if (!showScrollBootom && scrollTop >= 1) {
            setShowScrollBootom(true);
            return;
        }
        if (showScrollBootom && scrollTop < 1) {
            setShowScrollBootom(false);
            return;
        }
    }, 300);

    useEffect(() => {
        if (!messgeRef.current) {
            return;
        }
        const handleNetworkChange = () => {
            // Taro 未对 @taro/taro 进行模块化处理， 所以这里进行解构使用
            // 传送门 https://github.com/NervJS/taro/pull/17755
            getNetworkType({
                success: (res) => {
                    if (res.networkType !== 'none' && !messgeRef.current?.getConnectStatus()) {
                        console.log('网络恢复，尝试重连');
                        messgeRef.current?.connectWebsocket();
                    }
                }
            });
        };
        onNetworkStatusChange(handleNetworkChange);
        return () => offNetworkStatusChange(handleNetworkChange);
    }, []);

    useDidShow(() => {
        if (!messgeRef.current) {
            return;
        }
        const isConnected = messgeRef.current?.getConnectStatus();
        if (!isConnected) {
            messgeRef.current?.connectWebsocket();
        }
    });

    return (
        <View
            className={clsx([$style.chat, className])}
            style={style}
        >
            <KChatView
                ref={virtuaRef}
                historyApi={historyApi}
                onLoadHistory={updateHistoryHandle}
                onScroll={scrollHandle}
                onRefresh={refreshHandle}
                showPull={showPullHistory}
            >
                <ChatDataListComponent
                    dataList={dataList}
                    onStatusChange={statusChangeHandle}
                ></ChatDataListComponent>
            </KChatView>
            <View
                className={$style.footer}
                style={{
                    paddingBottom: `${keybordHeight ? keybordHeight : size}px`
                }}
            >
                {children}
                <KChatInput
                    disabled={outputStatus === 'start'}
                    onKeybordChange={keyboardChangeHandle}
                    onFocus={scrollToBottom}
                    onSend={send}
                ></KChatInput>
                {showScrollBootom && (
                    <View
                        className={$style['float-bottom']}
                        onClick={scrollToBottom}
                        onTouchEnd={(e) => {
                            e.preventDefault();
                            scrollToBottom();
                        }}
                    ></View>
                )}
            </View>
        </View>
    );
};
const KChat = forwardRef(Chat);
export type {
    MessageInfo,
    PluginType,
    SendBeforeItemType,
    MessageOptions,
    MessageErrorType,
    SendApiType,
    OutputRenderStatus,
    ChatConfig
} from './message/type';
export default KChat;
